import { Component, createRef, forwardRef, Fragment, memo, useEffect, useMemo, useRef, useState } from "react";
import { Group, Layer, Line, Rect, Stage, Image as KonvaImage, Circle } from "react-konva";
import { observer } from "mobx-react";
import { getEnv, getRoot, isAlive } from "mobx-state-tree";

import ImageGrid from "../ImageGrid/ImageGrid";
import ImageTransformer from "../ImageTransformer/ImageTransformer";
import ObjectTag from "../../components/Tags/Object";
import Tree from "../../core/Tree";
import styles from "./ImageView.module.scss";
import { errorBuilder } from "../../core/DataValidator/ConfigValidator";
import { chunks, findClosestParent } from "../../utils/utilities";
import Konva from "konva";
import { LoadingOutlined } from "@ant-design/icons";
import { Toolbar } from "../Toolbar/Toolbar";
import { ImageViewProvider } from "./ImageViewContext";
import { Hotkey } from "../../core/Hotkey";
import { useObserver } from "mobx-react";
import ResizeObserver from "../../utils/resize-observer";
import { debounce } from "../../utils/debounce";
import Constants from "../../core/Constants";
import { fixRectToFit, mapKonvaBrightness } from "../../utils/image";
import {
  FF_DEV_1442,
  FF_DEV_3077,
  FF_DEV_3793,
  FF_LSDV_4583_6,
  FF_LSDV_4711,
  FF_LSDV_4930,
  FF_ZOOM_OPTIM,
  isFF,
} from "../../utils/feature-flags";
import { Pagination } from "../../common/Pagination/Pagination";
import { Image } from "./Image";
import { isHoveringNonTransparentPixel } from "../../regions/BitmaskRegion/utils";
import { ff } from "@humansignal/core";
import { FF_BITMASK } from "@humansignal/core/lib/utils/feature-flags";

Konva.showWarnings = false;

const hotkeys = Hotkey("Image");
const imgDefaultProps = {};

if (isFF(FF_LSDV_4711)) imgDefaultProps.crossOrigin = "anonymous";

const splitRegions = (regions) => {
  const brushRegions = [];
  const shapeRegions = [];
  const bitmaskRegions = [];
  const vectorRegions = [];

  for (const region of regions) {
    switch (region.type) {
      case "brushregion":
        brushRegions.push(region);
        break;
      case "bitmaskregion":
        bitmaskRegions.push(region);
        break;
      default:
        shapeRegions.push(region);
        break;
    }
  }

  return {
    brushRegions,
    bitmaskRegions,
    vectorRegions,
    shapeRegions,
  };
};

const Region = memo(({ region, showSelected = false }) => {
  return useObserver(() => Tree.renderItem(region, region.annotation, true));
});

const RegionsLayer = memo(({ regions, name, useLayers, showSelected = false, smoothing = true }) => {
  const content = regions.map((el) => {
    return <Region key={`region-${el.id}`} region={el} showSelected={showSelected} />;
  });

  return useLayers === false ? (
    content
  ) : (
    <Layer name={name} imageSmoothingEnabled={smoothing}>
      {content}
    </Layer>
  );
});

const Regions = memo(
  ({ regions, useLayers = true, chunkSize = 15, suggestion = false, showSelected = false, smoothing = true }) => {
    return (
      <ImageViewProvider value={{ suggestion }}>
        {(chunkSize ? chunks(regions, chunkSize) : regions).map((chunk, i) => (
          <RegionsLayer
            key={`chunk-${i}`}
            name={`chunk-${i}`}
            regions={chunk}
            useLayers={useLayers}
            showSelected={showSelected}
            smoothing={smoothing}
          />
        ))}
      </ImageViewProvider>
    );
  },
);

const DrawingRegion = observer(({ item }) => {
  const { drawingRegion } = item;

  if (!drawingRegion) return null;
  if (item.multiImage && item.currentImage !== drawingRegion.item_index) return null;

  const isBrush = drawingRegion.type === "brushregion";
  const Wrapper = drawingRegion && isBrush ? Fragment : Layer;

  return (
    <Wrapper imageSmoothingEnabled={item.smoothing}>
      {drawingRegion ? <Region key={"drawing"} region={drawingRegion} /> : drawingRegion}
    </Wrapper>
  );
});

const SELECTION_COLOR = "#40A9FF";
const SELECTION_SECOND_COLOR = "white";
const SELECTION_DASH = [3, 3];

/**
 * Multiple selected regions when transform is unavailable — just a box with anchors
 */
const SelectionBorders = observer(({ item, selectionArea }) => {
  const { selectionBorders: bbox } = selectionArea;

  if (!isFF(FF_DEV_3793)) {
    bbox.left = bbox.left * item.stageScale;
    bbox.right = bbox.right * item.stageScale;
    bbox.top = bbox.top * item.stageScale;
    bbox.bottom = bbox.bottom * item.stageScale;
  }

  const points = bbox
    ? [
        {
          x: bbox.left,
          y: bbox.top,
        },
        {
          x: bbox.right,
          y: bbox.top,
        },
        {
          x: bbox.left,
          y: bbox.bottom,
        },
        {
          x: bbox.right,
          y: bbox.bottom,
        },
      ]
    : [];
  const ANCHOR_SIZE = isFF(FF_DEV_3793) ? 6 / item.stageScale : 6;

  return (
    <>
      {bbox && (
        <Rect
          name="regions_selection"
          x={bbox.left}
          y={bbox.top}
          width={bbox.right - bbox.left}
          height={bbox.bottom - bbox.top}
          stroke={SELECTION_COLOR}
          strokeWidth={1}
          strokeScaleEnabled={false}
          listening={false}
        />
      )}
      {points.map((point, idx) => {
        return (
          <Rect
            key={idx}
            x={point.x - ANCHOR_SIZE / 2}
            y={point.y - ANCHOR_SIZE / 2}
            width={ANCHOR_SIZE}
            height={ANCHOR_SIZE}
            fill={SELECTION_COLOR}
            stroke={SELECTION_SECOND_COLOR}
            strokeWidth={2}
            strokeScaleEnabled={false}
            listening={false}
          />
        );
      })}
    </>
  );
});

/**
 * Selection area during selection — dashed rect
 */
const SelectionRect = observer(({ item }) => {
  const { x, y, width, height } = item.onCanvasRect;

  const positionProps = {
    x,
    y,
    width,
    height,
    listening: false,
    strokeWidth: 1,
  };

  return (
    <>
      <Rect {...positionProps} stroke={SELECTION_COLOR} dash={SELECTION_DASH} strokeScaleEnabled={false} />
      <Rect
        {...positionProps}
        stroke={SELECTION_SECOND_COLOR}
        dash={SELECTION_DASH}
        dashOffset={SELECTION_DASH[0]}
        strokeScaleEnabled={false}
      />
    </>
  );
});

const TRANSFORMER_BACK_ID = "transformer_back";

const TransformerBack = observer(({ item }) => {
  const { selectedRegionsBBox } = item;
  const singleNodeMode = item.selectedRegions.length === 1;
  const dragStartPointRef = useRef({ x: 0, y: 0 });

  return (
    <Layer>
      {selectedRegionsBBox && !singleNodeMode && (
        <Rect
          id={TRANSFORMER_BACK_ID}
          fill="rgba(0,0,0,0)"
          draggable
          onClick={() => {
            item.annotation.unselectAreas();
          }}
          onMouseOver={(ev) => {
            if (!item.annotation.isLinkingMode) {
              ev.target.getStage().container().style.cursor = Constants.POINTER_CURSOR;
            }
          }}
          onMouseOut={(ev) => {
            ev.target.getStage().container().style.cursor = Constants.DEFAULT_CURSOR;
          }}
          onDragStart={(e) => {
            dragStartPointRef.current = {
              x: item.canvasToInternalX(e.target.getAttr("x")),
              y: item.canvasToInternalY(e.target.getAttr("y")),
            };
          }}
          dragBoundFunc={(pos) => {
            let { x, y } = pos;
            const { top, left, right, bottom } = item.selectedRegionsBBox;
            const { stageHeight, stageWidth } = item;

            const offset = {
              x: dragStartPointRef.current.x - left,
              y: dragStartPointRef.current.y - top,
            };

            x -= offset.x;
            y -= offset.y;

            const bbox = { x, y, width: right - left, height: bottom - top };

            const fixed = fixRectToFit(bbox, stageWidth, stageHeight);

            if (fixed.width !== bbox.width) {
              x += (fixed.width - bbox.width) * (fixed.x !== bbox.x ? -1 : 1);
            }

            if (fixed.height !== bbox.height) {
              y += (fixed.height - bbox.height) * (fixed.y !== bbox.y ? -1 : 1);
            }

            x += offset.x;
            y += offset.y;
            return { x, y };
          }}
        />
      )}
    </Layer>
  );
});

const SelectedRegions = observer(({ item, selectedRegions }) => {
  if (!selectedRegions) return null;
  const { brushRegions = [], shapeRegions = [] } = splitRegions(selectedRegions);

  return (
    <>
      {isFF(FF_LSDV_4930) ? null : <TransformerBack item={item} />}
      {brushRegions.length > 0 && (
        <Regions key="brushes" name="brushes" regions={brushRegions} useLayers={false} showSelected chankSize={0} />
      )}

      {shapeRegions.length > 0 && (
        <Regions key="shapes" name="shapes" regions={shapeRegions} showSelected chankSize={0} />
      )}
    </>
  );
});

const SelectionLayer = observer(({ item, selectionArea }) => {
  const scale = isFF(FF_DEV_3793) ? 1 : 1 / (item.zoomScale || 1);
  const [isMouseWheelClick, setIsMouseWheelClick] = useState(false);
  const [shift, setShift] = useState(false);
  const isPanTool = item.getToolsManager().findSelectedTool()?.fullName === "ZoomPanTool";

  const dragHandler = (e) => setIsMouseWheelClick(e.buttons === 4);

  const handleKey = (e) => setShift(e.shiftKey);

  useEffect(() => {
    window.addEventListener("keydown", handleKey);
    window.addEventListener("keyup", handleKey);
    window.addEventListener("mousedown", dragHandler);
    window.addEventListener("mouseup", dragHandler);
    return () => {
      window.removeEventListener("keydown", handleKey);
      window.removeEventListener("keyup", handleKey);
      window.removeEventListener("mousedown", dragHandler);
      window.removeEventListener("mouseup", dragHandler);
    };
  }, []);

  const disableTransform = item.zoomScale > 1 && (shift || isPanTool || isMouseWheelClick);

  let supportsTransform = true;
  let supportsRotate = true;
  let supportsScale = true;

  item.selectedRegions?.forEach((shape) => {
    supportsTransform = supportsTransform && shape.supportsTransform === true;
    supportsRotate = supportsRotate && shape.canRotate === true;
    supportsScale = supportsScale && true;
  });

  supportsTransform =
    supportsTransform &&
    (item.selectedRegions.length > 1 ||
      ((item.useTransformer || item.selectedShape?.preferTransformer) && item.selectedShape?.useTransformer));

  return (
    <Layer scaleX={scale} scaleY={scale}>
      {selectionArea.isActive ? (
        <SelectionRect item={selectionArea} />
      ) : !supportsTransform && item.selectedRegions.length > 1 ? (
        <SelectionBorders item={item} selectionArea={selectionArea} />
      ) : null}
      <ImageTransformer
        item={item}
        rotateEnabled={supportsRotate}
        supportsTransform={!disableTransform && supportsTransform}
        supportsScale={supportsScale}
        selectedShapes={item.selectedRegions}
        singleNodeMode={item.selectedRegions.length === 1}
        useSingleNodeRotation={item.selectedRegions.length === 1 && supportsRotate}
        draggableBackgroundSelector={`#${TRANSFORMER_BACK_ID}`}
      />
    </Layer>
  );
});

/**
 * Previously regions rerendered on window resize because of size recalculations,
 * but now they are rerendered just by mistake because of unmemoized `splitRegions` in main render.
 * This is temporary solution to pass in relevant props changed on window resize.
 */
const Selection = observer(({ item, ...triggeredOnResize }) => {
  const { selectionArea } = item;

  return (
    <>
      <Layer name="selection-regions-layer" />
      <SelectionLayer item={item} selectionArea={selectionArea} />
    </>
  );
});

const Crosshair = memo(
  forwardRef(({ width, height }, ref) => {
    const [pointsV, setPointsV] = useState([50, 0, 50, height]);
    const [pointsH, setPointsH] = useState([0, 100, width, 100]);
    const [x, setX] = useState(100);
    const [y, setY] = useState(50);

    const [visible, setVisible] = useState(false);
    const strokeWidth = 1;
    const dashStyle = [3, 3];
    const enableStrokeScale = false;

    if (ref) {
      ref.current = {
        updatePointer(newX, newY) {
          if (newX !== x) {
            setX(newX);
            setPointsV([newX, 0, newX, height]);
          }

          if (newY !== y) {
            setY(newY);
            setPointsH([0, newY, width, newY]);
          }
        },
        updateVisibility(visibility) {
          setVisible(visibility);
        },
      };
    }

    return (
      <Layer name="crosshair" listening={false} opacity={visible ? 0.6 : 0}>
        <Group>
          <Line
            name="v-white"
            points={pointsH}
            stroke="#fff"
            strokeWidth={strokeWidth}
            strokeScaleEnabled={enableStrokeScale}
          />
          <Line
            name="v-black"
            points={pointsH}
            stroke="#000"
            strokeWidth={strokeWidth}
            dash={dashStyle}
            strokeScaleEnabled={enableStrokeScale}
          />
        </Group>
        <Group>
          <Line
            name="h-white"
            points={pointsV}
            stroke="#fff"
            strokeWidth={strokeWidth}
            strokeScaleEnabled={enableStrokeScale}
          />
          <Line
            name="h-black"
            points={pointsV}
            stroke="#000"
            strokeWidth={strokeWidth}
            dash={dashStyle}
            strokeScaleEnabled={enableStrokeScale}
          />
        </Group>
      </Layer>
    );
  }),
);

const PixelGridLayer = observer(({ item }) => {
  const ZOOM_THRESHOLD = 20;

  const visible = item.zoomScale > ZOOM_THRESHOLD;
  const { naturalWidth, naturalHeight } = item.currentImageEntity ?? {};
  const { stageWidth, stageHeight } = item;
  const imageSmallerThanStage = naturalWidth < stageWidth || naturalHeight < stageHeight;

  const step = item.stageZoom; // image pixel

  const { verticalPoints, horizontalPoints } = useMemo(() => {
    const vPts = [];
    const hPts = [];

    // Grid starts from image origin (0, 0)
    for (let x = 0; x <= stageWidth; x += step) {
      vPts.push(x, 0, x, stageHeight, x, 0);
    }

    for (let y = 0; y <= stageHeight; y += step) {
      hPts.push(0, y, stageWidth, y, 0, y);
    }

    return { verticalPoints: vPts, horizontalPoints: hPts };
  }, [stageWidth, stageHeight]);

  return (
    <Layer listening={false} visible={visible}>
      <Line
        points={verticalPoints}
        stroke="white"
        strokeWidth={1}
        strokeScaleEnabled={false}
        perfectDrawEnabled={false}
        opacity={0.2}
        listening={false}
        visible={visible}
      />
      <Line
        points={horizontalPoints}
        stroke="white"
        strokeWidth={1}
        strokeScaleEnabled={false}
        perfectDrawEnabled={false}
        opacity={0.2}
        listening={false}
        visible={visible}
      />
    </Layer>
  );
});

/**
 * Component that creates an overlay on top
 * of the image to support Magic Wand tool
 */
const CanvasOverlay = observer(({ item }) => {
  return (
    <canvas
      className={styles.overlay}
      ref={(ref) => {
        item.setOverlayRef(ref);
      }}
      style={item.imageTransform}
    />
  );
});

export default observer(
  class ImageView extends Component {
    // stored position of canvas before creating region
    canvasX;
    canvasY;
    lastOffsetWidth = -1;
    lastOffsetHeight = -1;
    state = {
      imgStyle: {},
      pointer: [0, 0],
    };

    imageRef = createRef();
    crosshairRef = createRef();
    handleDeferredMouseDown = null;
    deferredClickTimeout = [];
    skipNextMouseDown = false;
    skipNextClick = false;
    skipNextMouseUp = false;
    mouseDownPoint = null;

    constructor(props) {
      super(props);

      if (typeof props.item.smoothing === "boolean") props.store.settings.setSmoothing(props.item.smoothing);
    }

    handleOnClick = (e) => {
      const { item } = this.props;

      if (isFF(FF_DEV_1442)) {
        this.handleDeferredMouseDown?.(true);
      }
      if (this.skipNextClick) {
        this.skipNextClick = false;
        return;
      }

      const evt = e.evt || e;
      const { offsetX: x, offsetY: y } = evt;

      if (isFF(FF_LSDV_4930)) {
        // Konva can trigger click even on simple mouseup
        // You can try drag and drop interaction here https://konvajs.org/docs/events/Stage_Events.html and check the console
        // So here is false trigger preventing
        if (
          !this.mouseDownPoint ||
          Math.abs(this.mouseDownPoint.x - x) > 0.01 ||
          Math.abs(this.mouseDownPoint.y - y) > 0.01
        ) {
          this.mouseDownPoint = null;
          return;
        }
      }

      // We can only handle Bitmask selection here because the way it works -- it overlays an
      // entire stage with a single image that is not click-through, and there is no particular
      // shape we can click on. Here we're relying on cursor position and non-transparent pixels
      // of the mask to detect cursor-region collision.
      if (ff.isActive(FF_BITMASK)) {
        const hasSelected = item.selectedRegions.some((r) => r.type === "bitmaskregion");
        const isBitmask = ["BitmaskTool", "BitmaskEraserTool"].includes(
          item.getToolsManager().findSelectedTool().toolName,
        );

        // We want to avoid weird behavior here with drawing while selecting another region
        // so we just do nothing when clicked outside AND we have a tool selected
        if (hasSelected && isBitmask) {
          return;
        }

        const hoveredRegion = item.regs.find((reg) => {
          if (reg.type !== "bitmaskregion") return false;
          if (reg.selected) return false;

          return isHoveringNonTransparentPixel(reg);
        });

        if (hoveredRegion) {
          hoveredRegion.onClickRegion(e);
          return;
        }
      }
      return item.event("click", evt, x, y);
    };

    resetDeferredClickTimeout = () => {
      if (this.deferredClickTimeout.length > 0) {
        this.deferredClickTimeout = this.deferredClickTimeout.filter((timeout) => {
          clearTimeout(timeout);
          return false;
        });
      }
    };

    handleDeferredClick = (handleDeferredMouseDownCallback, handleDeselection, eligibleToDeselect = false) => {
      this.handleDeferredMouseDown = (wasClicked) => {
        if (wasClicked && eligibleToDeselect) {
          handleDeselection();
        }
        handleDeferredMouseDownCallback();
        // mousedown should be called only once especially if it is called from mousemove interaction.
        this.handleDeferredMouseDown = null;
      };
      this.resetDeferredClickTimeout();
      this.deferredClickTimeout.push(
        setTimeout(
          () => {
            this.handleDeferredMouseDown?.(false);
          },
          this.props.item.annotation.isDrawing ? 0 : 100,
        ),
      );
    };

    handleMouseDown = (e) => {
      const { item } = this.props;
      const isPanTool = item.getToolsManager().findSelectedTool()?.fullName === "ZoomPanTool";
      const isMoveTool = item.getToolsManager().findSelectedTool()?.fullName === "MoveTool";

      this.skipNextMouseDown = this.skipNextMouseUp = this.skipNextClick = false;
      if (isFF(FF_LSDV_4930)) {
        this.mouseDownPoint = { x: e.evt.offsetX, y: e.evt.offsetY };
      }

      item.updateSkipInteractions(e);

      const p = e.target.getParent();

      if (item.annotation.isReadOnly() && !isPanTool) return;
      if (p && p.className === "Transformer") return;

      const handleMouseDown = () => {
        if (e.evt.button === 1) {
          // prevent middle click from scrolling page
          e.evt.preventDefault();
        }

        const isRightElementToCatchToolInteractions = (el) => {
          // Bitmask is like Brush, so treat it the same
          // The only difference is that Bitmask doesn't have a group inside
          if (el.nodeType === "Layer" && !isMoveTool && el.attrs?.name === "bitmask") {
            return true;
          }

          // It could be ruler ot segmentation
          if (el.nodeType === "Group") {
            if (el?.attrs?.name === "ruler") {
              return true;
            }
            // segmentation is specific for Brushes
            // but click interaction on the region covers the case of the same MoveTool interaction here,
            // so it should ignore move tool interaction to prevent conflicts
            if (!isMoveTool && el?.attrs?.name === "segmentation") {
              return true;
            }
          }
          return false;
        };

        if (
          // create regions over another regions with Cmd/Ctrl pressed
          item.getSkipInteractions() ||
          e.target === item.stageRef ||
          findClosestParent(e.target, isRightElementToCatchToolInteractions)
        ) {
          window.addEventListener("mousemove", this.handleGlobalMouseMove);
          window.addEventListener("mouseup", this.handleGlobalMouseUp);
          const { offsetX: x, offsetY: y } = e.evt;
          // store the canvas coords for calculations in further events
          const { left, top } = item.containerRef.getBoundingClientRect();

          this.canvasX = left;
          this.canvasY = top;
          if (this.skipNextMouseDown) {
            this.skipNextMouseDown = false;
            return true;
          }
          item.event("mousedown", e, x, y);

          return true;
        }
      };

      const selectedTool = item.getToolsManager().findSelectedTool();
      const eligibleToolForDeselect = [
        undefined,
        "EllipseTool",
        "EllipseTool-dynamic",
        "RectangleTool",
        "RectangleTool-dynamic",
        "PolygonTool",
        "PolygonTool-dynamic",
        "Rectangle3PointTool",
        "Rectangle3PointTool-dynamic",
      ].includes(selectedTool?.fullName);

      if (isFF(FF_DEV_1442) && eligibleToolForDeselect) {
        const targetIsCanvas = e.target === item.stageRef;
        const annotationHasSelectedRegions = item.annotation.selectedRegions.length > 0;
        const eligibleToDeselect = targetIsCanvas && annotationHasSelectedRegions;

        const handleDeselection = () => {
          item.annotation.unselectAll();
          this.skipNextMouseDown = true;
          this.skipNextMouseUp = true;
          this.skipNextClick = true;
        };

        this.handleDeferredClick(handleMouseDown, handleDeselection, eligibleToDeselect);
        return;
      }

      const result = handleMouseDown();

      if (result) return result;

      return true;
    };

    /**
     * Mouse up outside the canvas
     */
    handleGlobalMouseUp = (e) => {
      window.removeEventListener("mousemove", this.handleGlobalMouseMove);
      window.removeEventListener("mouseup", this.handleGlobalMouseUp);

      if (e.target && e.target.tagName === "CANVAS") return;

      const { item } = this.props;
      const { clientX: x, clientY: y } = e;

      item.freezeHistory();

      return this.triggerMouseUp(e, x - this.canvasX, y - this.canvasY);
    };

    handleGlobalMouseMove = (e) => {
      if (e.target && e.target.tagName === "CANVAS") return;

      const { item } = this.props;
      const { clientX: x, clientY: y } = e;

      return item.event("mousemove", e, x - this.canvasX, y - this.canvasY);
    };

    /**
     * Mouse up on Stage
     */
    handleMouseUp = (e) => {
      const { item } = this.props;

      if (isFF(FF_DEV_1442)) {
        this.resetDeferredClickTimeout();
      }

      item.freezeHistory();

      return this.triggerMouseUp(e, e.evt.offsetX, e.evt.offsetY);
    };

    triggerMouseUp = (e, x, y) => {
      if (this.skipNextMouseUp) {
        this.skipNextMouseUp = false;
        return;
      }
      const { item } = this.props;

      return item.event("mouseup", e, x, y);
    };

    handleMouseMove = (e) => {
      const { item } = this.props;

      item.freezeHistory();

      this.updateCrosshair(e);

      const isMouseWheelClick = e.evt && e.evt.buttons === 4;
      const isDragging = e.evt && e.evt.buttons === 1;
      const isShiftDrag = isDragging && e.evt.shiftKey;

      if (isFF(FF_DEV_1442) && isDragging) {
        this.resetDeferredClickTimeout();
        this.handleDeferredMouseDown?.(false);
      }

      if ((isMouseWheelClick || isShiftDrag) && item.zoomScale > 1) {
        item.setSkipInteractions(true);
        e.evt.preventDefault();

        const newPos = {
          x: item.zoomingPositionX + e.evt.movementX,
          y: item.zoomingPositionY + e.evt.movementY,
        };

        item.setZoomPosition(newPos.x, newPos.y);
      } else {
        item.event("mousemove", e, e.evt.offsetX, e.evt.offsetY);
      }

      // Handle Bitmask hover
      // We can only do it here due to Bitmask implementation. See `self.handleOnClick` method
      // for a full explanation.
      if (!e.evt.ctrlKey && !e.evt.shiftKey && ff.isActive(FF_BITMASK)) {
        if (item.regs.some((r) => r.isDrawing)) return;
        if (!item.regs.some((r) => r.type === "bitmaskregion")) return;
        requestAnimationFrame(() => {
          for (const region of item.regs) {
            region.setHighlight(false);
            region.updateCursor(false);
          }
          for (const region of item.regs) {
            if (region.type !== "bitmaskregion") continue;

            const checkHover = !region.selected && !region.isDrawing;
            const hovered = checkHover && isHoveringNonTransparentPixel(region);

            if (hovered) {
              // region.setHighlight(true);
              region.updateCursor(true);
              break;
            }
          }
        });
      }
    };

    updateCrosshair = (e) => {
      if (this.crosshairRef.current) {
        const { x, y } = e.currentTarget.getPointerPosition();
        this.crosshairRef.current.updatePointer(...this.props.item.fixZoomedCoords([x, y]));
      }
    };

    handleError = () => {
      const { item, store } = this.props;
      const cs = store.annotationStore;
      const message = getEnv(store).messages.ERR_LOADING_HTTP({
        attr: item.value,
        error: "",
        url: item.currentSrc,
      });

      cs.addErrors([errorBuilder.generalError(message)]);
    };

    updateGridSize = (range) => {
      const { item } = this.props;

      item.freezeHistory();

      item.setGridSize(range);
    };

    /**
     * Handle zoom and pan events from mouse wheel and trackpad
     * Supports:
     * - Ctrl/Cmd + mouse wheel: Smooth zoom in/out
     * - Ctrl/Cmd + trackpad pinch: Smooth pinch-to-zoom
     * - Two-finger scroll: Pan the image when zoomed in
     */
    handleZoom = (e) => {
      if (e.evt?.ctrlKey || e.evt?.metaKey) {
        e.evt.preventDefault();

        const { item } = this.props;
        const stage = item.stageRef;

        // Unified smooth zoom behavior for both trackpad and mouse wheel
        item.handleZoom(e.evt.deltaY, stage.getPointerPosition(), e.evt.ctrlKey);
      } else if (e.evt) {
        // Two fingers scroll (panning) - only when zoomed in
        const { item } = this.props;

        const maxScrollX = Math.round(item.stageWidth * item.zoomScale) - item.stageWidth;
        const maxScrollY = Math.round(item.stageHeight * item.zoomScale) - item.stageHeight;

        const newPos = {
          x: Math.min(0, Math.ceil(item.zoomingPositionX - e.evt.deltaX)),
          y: Math.min(0, Math.ceil(item.zoomingPositionY - e.evt.deltaY)),
        };

        // Calculate scroll boundaries to allow scrolling the page when reaching stage edges
        const withinX = newPos.x !== 0 && newPos.x > -maxScrollX && item.zoomScale !== 1;
        const withinY = newPos.y !== 0 && newPos.y > -maxScrollY && item.zoomScale !== 1;

        // Detect scroll direction
        const scrollingX = Math.abs(e.evt.deltaX) > Math.abs(e.evt.deltaY);
        const scrollingY = Math.abs(e.evt.deltaY) > Math.abs(e.evt.deltaX);

        if (withinX && scrollingX) e.evt.preventDefault();
        if (withinY && scrollingY) e.evt.preventDefault();

        item.setZoomPosition(newPos.x, newPos.y);
      }
    };

    renderRulers() {
      const { item } = this.props;
      const width = 1;
      const color = "white";

      return (
        <Group
          name="ruler"
          onClick={(ev) => {
            ev.cancelBubble = false;
          }}
        >
          <Line
            x={0}
            y={item.cursorPositionY}
            points={[0, 0, item.stageWidth, 0]}
            strokeWidth={width}
            stroke={color}
            tension={0}
            dash={[4, 4]}
            closed
          />
          <Line
            x={item.cursorPositionX}
            y={0}
            points={[0, 0, 0, item.stageHeight]}
            strokeWidth={width}
            stroke={color}
            tension={0}
            dash={[1.5]}
            closed
          />
        </Group>
      );
    }

    onResize = debounce(() => {
      requestAnimationFrame(() => {
        if (!this?.props?.item?.containerRef) return;
        const { offsetWidth, offsetHeight } = this.props.item.containerRef;

        if (this.props.item.naturalWidth <= 1) return;
        if (this.lastOffsetWidth === offsetWidth && this.lastOffsetHeight === offsetHeight) return;

        this.props.item.onResize(offsetWidth, offsetHeight, true);
        this.lastOffsetWidth = offsetWidth;
        this.lastOffsetHeight = offsetHeight;
      });
    }, 16);

    componentDidMount() {
      const { item } = this.props;

      window.addEventListener("resize", this.onResize);
      this.attachObserver(item.containerRef);
      this.updateReadyStatus();

      hotkeys.addDescription("shift", "Pan image");
    }

    attachObserver = (node) => {
      if (this.resizeObserver) this.detachObserver();

      if (node) {
        this.resizeObserver = new ResizeObserver(this.onResize);
        this.resizeObserver.observe(node);
      }
    };

    detachObserver = () => {
      if (this.resizeObserver) {
        this.resizeObserver.disconnect();
        this.resizeObserver = null;
      }
    };

    componentWillUnmount() {
      this.detachObserver();
      window.removeEventListener("resize", this.onResize);

      hotkeys.removeDescription("shift");
    }

    componentDidUpdate() {
      this.onResize();
      this.updateReadyStatus();
    }

    updateReadyStatus() {
      const { item } = this.props;
      const { imageRef } = this;

      if (!item || !isAlive(item) || !imageRef.current) return;
      if (item.isReady !== imageRef.current.complete) item.setReady(imageRef.current.complete);
    }

    renderTools() {
      const { item, store } = this.props;

      if (store.annotationStore.viewingAll) return null;

      const tools = item.getToolsManager().allTools();

      return <Toolbar tools={tools} />;
    }

    render() {
      const { item, store } = this.props;

      // @todo stupid but required check for `resetState()`
      // when Image tries to render itself after detouching
      if (!isAlive(item)) return null;

      // TODO fix me
      if (!store.task || !item.currentSrc) return null;

      const containerStyle = {};

      const containerClassName = styles.container;

      const paginationEnabled = !!item.isMultiItem;

      if (getRoot(item).settings.fullscreen === false) {
        containerStyle.maxWidth = item.maxwidth;
        containerStyle.maxHeight = item.maxheight;
        containerStyle.width = item.width;
        containerStyle.height = item.height;
      }

      if (!store.settings.enableSmoothing && item.zoomScale > 1) {
        containerStyle.imageRendering = "pixelated";
      }

      const imagePositionClassnames = [
        styles.image_position,
        styles[`image_position__${item.verticalalignment === "center" ? "middle" : item.verticalalignment}`],
        styles[`image_position__${item.horizontalalignment}`],
      ];

      const wrapperClasses = [styles.wrapperComponent, item.images.length > 1 ? styles.withGallery : styles.wrapper];

      if (paginationEnabled) wrapperClasses.push(styles.withPagination);

      const [toolsReady, stageLoading] = isFF(FF_LSDV_4583_6) ? [true, false] : [item.hasTools, item.stageWidth <= 1];

      const imageIsLoaded = item.imageIsLoaded || !isFF(FF_LSDV_4583_6);
      const isViewingAll = store.annotationStore.viewingAll;

      return (
        <ObjectTag item={item} className={wrapperClasses.join(" ")}>
          {paginationEnabled ? (
            <div
              className={styles.pagination}
              title={isViewingAll ? "Pagination is not supported in View All Annotations" : undefined}
            >
              <Pagination
                size="small"
                outline={false}
                align="left"
                noPadding
                hotkey={{
                  prev: "image:prev",
                  next: "image:next",
                }}
                currentPage={item.currentImage + 1}
                totalPages={item.parsedValueList.length}
                onChange={(n) => item.setCurrentImage(n - 1)}
                pageSizeSelectable={false}
                disabled={isViewingAll}
              />
            </div>
          ) : null}

          <div
            ref={(node) => {
              item.setContainerRef(node);
              this.attachObserver(node);
            }}
            className={containerClassName}
            style={containerStyle}
          >
            <div
              ref={(node) => {
                this.filler = node;
              }}
              className={styles.filler}
              style={{ width: "100%", marginTop: item.fillerHeight }}
            />

            {isFF(FF_LSDV_4583_6) ? (
              <Image
                ref={(ref) => {
                  item.setImageRef(ref);
                  this.imageRef.current = ref;
                }}
                usedValue={item.usedValue}
                imageEntity={item.currentImageEntity}
                imageTransform={item.imageTransform}
                updateImageSize={item.updateImageSize}
                size={item.canvasSize}
                overlay={<CanvasOverlay item={item} />}
              />
            ) : (
              <div className={[styles.frame, ...imagePositionClassnames].join(" ")} style={item.canvasSize}>
                <img
                  ref={(ref) => {
                    item.setImageRef(ref);
                    this.imageRef.current = ref;
                  }}
                  loading={isFF(FF_DEV_3077) && !item.lazyoff ? "lazy" : "false"}
                  style={item.imageTransform}
                  src={item.currentSrc}
                  onLoad={(e) => {
                    item.updateImageSize(e);
                    item.currentImageEntity.setImageLoaded(true);
                  }}
                  onError={this.handleError}
                  crossOrigin={item.imageCrossOrigin}
                  alt="LS"
                />
                <CanvasOverlay item={item} />
              </div>
            )}
            {/* @todo this is dirty hack; rewrite to proper async waiting for data to load */}
            {stageLoading || !toolsReady ? (
              <div className={styles.loading}>
                <LoadingOutlined />
              </div>
            ) : imageIsLoaded ? (
              <EntireStage
                item={item}
                crosshairRef={this.crosshairRef}
                onClick={this.handleOnClick}
                imagePositionClassnames={imagePositionClassnames}
                state={this.state}
                onMouseEnter={() => {
                  if (this.crosshairRef.current) {
                    this.crosshairRef.current.updateVisibility(true);
                  }
                }}
                onMouseLeave={(e) => {
                  if (this.crosshairRef.current) {
                    this.crosshairRef.current.updateVisibility(false);
                  }
                  const { width: stageWidth, height: stageHeight } = item.canvasSize;
                  const { offsetX: mouseposX, offsetY: mouseposY } = e.evt;
                  const newEvent = { ...e };

                  if (mouseposX <= 0) {
                    e.offsetX = 0;
                  } else if (mouseposX >= stageWidth) {
                    e.offsetX = stageWidth;
                  }

                  if (mouseposY <= 0) {
                    e.offsetY = 0;
                  } else if (mouseposY >= stageHeight) {
                    e.offsetY = stageHeight;
                  }
                  this.handleMouseMove(newEvent);
                }}
                onDragMove={this.updateCrosshair}
                onMouseDown={this.handleMouseDown}
                onMouseMove={this.handleMouseMove}
                onMouseUp={this.handleMouseUp}
                onWheel={item.zoom ? this.handleZoom : () => {}}
              />
            ) : null}
          </div>

          {toolsReady && imageIsLoaded && this.renderTools()}
          {item.images.length > 1 && (
            <div className={styles.gallery}>
              {item.images.map((src, i) => (
                <img
                  {...imgDefaultProps}
                  alt=""
                  key={src}
                  src={src}
                  className={i === item.currentImage && styles.active}
                  height="60"
                  onClick={() => item.setCurrentImage(i)}
                />
              ))}
            </div>
          )}
        </ObjectTag>
      );
    }
  },
);

const EntireStage = observer(
  ({
    item,
    imagePositionClassnames,
    state,
    onClick,
    onMouseEnter,
    onMouseLeave,
    onDragMove,
    onMouseDown,
    onMouseMove,
    onMouseUp,
    onWheel,
    crosshairRef,
  }) => {
    const { store } = item;
    let size;
    let position;

    if (isFF(FF_ZOOM_OPTIM)) {
      size = {
        width: item.containerWidth,
        height: item.containerHeight,
      };
      position = {
        x: item.zoomingPositionX + item.alignmentOffset.x,
        y: item.zoomingPositionY + item.alignmentOffset.y,
      };
    } else {
      size = { ...item.canvasSize };
      position = {
        x: item.zoomingPositionX,
        y: item.zoomingPositionY,
      };
    }

    return (
      <Stage
        ref={(ref) => {
          item.setStageRef(ref);
        }}
        className={[styles["image-element"], ...imagePositionClassnames].join(" ")}
        width={size.width}
        height={size.height}
        scaleX={item.zoomScale}
        scaleY={item.zoomScale}
        x={position.x}
        y={position.y}
        offsetX={item.stageTranslate.x}
        offsetY={item.stageTranslate.y}
        rotation={item.rotation}
        onClick={onClick}
        onMouseEnter={onMouseEnter}
        onMouseLeave={onMouseLeave}
        onDragMove={onDragMove}
        onMouseDown={onMouseDown}
        onMouseMove={onMouseMove}
        onMouseUp={onMouseUp}
        onWheel={onWheel}
      >
        <StageContent item={item} store={store} state={state} crosshairRef={crosshairRef} />
      </Stage>
    );
  },
);

const ImageLayer = observer(({ item }) => {
  const imageEntity = item.currentImageEntity;
  const konvaImageRef = useRef();
  const [loadedImage, setLoadedImage] = useState(null);

  // Load image with proper CORS and load event
  useEffect(() => {
    if (imageEntity?.downloaded && imageEntity.currentSrc) {
      const img = new window.Image();
      img.crossOrigin = "anonymous";
      img.src = imageEntity.currentSrc;
      img.width = imageEntity.naturalWidth;
      img.height = imageEntity.naturalHeight;
      img.onload = () => {
        setLoadedImage(img);
      };
    } else {
      setLoadedImage(null);
    }
  }, [imageEntity?.downloaded, imageEntity?.currentSrc]);

  const { width, height } = useMemo(() => {
    return {
      width: imageEntity.naturalWidth,
      height: imageEntity.naturalHeight,
    };
  }, [imageEntity.naturalWidth, imageEntity.naturalHeight, item.stageWidth, item.stageHeight]);

  const brightness = mapKonvaBrightness(imageEntity.brightnessGrade);
  const contrast = imageEntity.contrastGrade - 100;

  useEffect(() => {
    const node = konvaImageRef.current;
    if (node && loadedImage) {
      node.cache({ pixelRatio: 1 });
      node.filters([Konva.Filters.Brighten, Konva.Filters.Contrast]);
      node.brightness(brightness);
      node.contrast(contrast);
      node.getLayer()?.batchDraw();
    }
  }, [loadedImage, brightness, contrast]);

  return loadedImage ? (
    <Layer imageSmoothingEnabled={item.smoothing} scale={{ x: item.stageZoom, y: item.stageZoom }}>
      <KonvaImage ref={konvaImageRef} image={loadedImage} width={width} height={height} listening={false} />
    </Layer>
  ) : null;
});

const CursorLayer = observer(({ item, tool }) => {
  const [[x, y], setCursorPosition] = useState([0, 0]);
  const [visible, setVisible] = useState(false);

  useEffect(() => {
    if (!item.stageRef) return;
    const stage = item.stageRef;
    const onMouseMove = (e) => {
      const { x, y } = stage.getPointerPosition();
      const { x: deltaX, y: deltaY } = stage.position();
      const { x: scaleX, y: scaleY } = stage.scale();
      setCursorPosition([(x - deltaX) / scaleX, (y - deltaY) / scaleY]);
    };
    const onMouseEnter = () => {
      setVisible(true);
    };
    const onMouseLeave = () => {
      setVisible(false);
    };

    stage.on("mousemove", onMouseMove);
    stage.on("mouseenter", onMouseEnter);
    stage.on("mouseleave", onMouseLeave);

    return () => {
      stage.off("mousemove", onMouseMove);
      stage.off("mouseenter", onMouseEnter);
      stage.off("mouseleave", onMouseLeave);
    };
  }, [item.stageRef]);

  const size = useMemo(() => {
    return tool.strokeWidth * item.stageZoom;
  }, [tool.strokeWidth, item.stageZoom]);

  return visible ? (
    <Layer listening={false}>
      {tool.strokeWidth <= 2 ? (
        <>
          <Rect
            x={x - size / 2}
            y={y - size / 2}
            width={size}
            height={size}
            stroke="black"
            strokeWidth={3}
            strokeScaleEnabled={false}
          />
          <Rect
            x={x - size / 2}
            y={y - size / 2}
            width={size}
            height={size}
            stroke="white"
            strokeWidth={1}
            strokeScaleEnabled={false}
          />
        </>
      ) : (
        <>
          <Circle x={x} y={y} radius={size} stroke="black" strokeWidth={3} strokeScaleEnabled={false} />
          <Circle x={x} y={y} radius={size} stroke="white" strokeWidth={1} strokeScaleEnabled={false} />
        </>
      )}
    </Layer>
  ) : null;
});

const StageContent = observer(({ item, store, state, crosshairRef }) => {
  if (!isAlive(item)) return null;
  if (!store.task || !item.currentSrc) return null;

  // Keep selected or highlighted region on top
  const regions = [...item.regs].sort((r) => (r.highlighted || r.selected ? 1 : -1));
  const paginationEnabled = !!item.isMultiItem;
  const wrapperClasses = [styles.wrapperComponent, item.images.length > 1 ? styles.withGallery : styles.wrapper];
  const tool = item.getToolsManager().findSelectedTool();

  if (paginationEnabled) wrapperClasses.push(styles.withPagination);

  const { brushRegions, shapeRegions, bitmaskRegions } = splitRegions(regions);

  const {
    brushRegions: suggestedBrushRegions,
    shapeRegions: suggestedShapeRegions,
    bitmaskRegions: suggestedBitmaskRegions,
  } = splitRegions(item.suggestions);

  const renderableRegions = Object.entries({
    brush: brushRegions,
    shape: shapeRegions,
    bitmask: bitmaskRegions,
    suggestedBrush: suggestedBrushRegions,
    suggestedBismask: suggestedBitmaskRegions,
    suggestedShape: suggestedShapeRegions,
  });

  return (
    <>
      {ff.isActive(ff.FF_BITMASK) && <ImageLayer item={item} />}
      {item.grid && item.sizeUpdated && <ImageGrid item={item} />}

      {isFF(FF_LSDV_4930) ? <TransformerBack item={item} /> : null}

      {renderableRegions.map(([groupName, list]) => {
        const useLayers = groupName.match(/brush/i) === null;
        const isSuggestion = groupName.match("suggested") !== null;

        return list.length > 0 ? (
          <Regions
            key={groupName}
            name={groupName}
            regions={list}
            useLayers={useLayers}
            suggestion={isSuggestion}
            smoothing={item.smoothing}
          />
        ) : (
          <Fragment key={groupName} />
        );
      })}
      <Selection item={item} isPanning={state.isPanning} />
      <DrawingRegion item={item} />
      {ff.isActive(ff.FF_BITMASK) && item.smoothing === false && <PixelGridLayer item={item} />}

      {item.crosshair && (
        <Crosshair
          ref={crosshairRef}
          width={isFF(FF_ZOOM_OPTIM) ? item.containerWidth : item.stageWidth}
          height={isFF(FF_ZOOM_OPTIM) ? item.containerHeight : item.stageHeight}
        />
      )}

      {tool && tool.toolName.match(/bitmask/i) && <CursorLayer item={item} tool={tool} />}
    </>
  );
});
